package com.google.code.guava.beans;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;

import com.google.code.guava.beans.Property.PropertyToName;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;

public final class Properties{

    private Properties(){}

    public static <X> Property<X> getPropertyByName(final BeanInfo beanInfo,
        final String propertyName){
        return new Property<X>(
            Introspection.findDescriptor(checkNotNull(beanInfo),//
                checkNotNull(propertyName)//
            ));
    }

    public static <X> Property<X> getPropertyByName(final Class<?> clazz,
        final String propertyName){
        return new Property<X>(//
            Introspection.findDescriptor(Introspection.getBeanInfo(//
                checkNotNull(clazz)),//
                checkNotNull(propertyName)//
            )
        );
    }

    public static <X> Property<X> getPropertyByName(final Object bean,
        final String propertyName){
        return new Property<X>(//
            Introspection.findDescriptor(//
                checkNotNull(bean).getClass(),//
                checkNotNull(propertyName)//
            )//
        );
    }

    public static ImmutableSortedSet<String> getPropertyNames(final Object bean){
        return ImmutableSortedSet.copyOf(Iterables.transform(//
            Introspection.listPropertyDescriptors(bean),
            PropertyToName.INSTANCE)//
        );
    }

    public static <X> Property<X> getPropertyfromDescriptor(
        final PropertyDescriptor descriptor){
        return new Property<X>(descriptor);
    }

    /**
     * Factory method: get a named Property of the specified type from a Bean
     * object.
     * 
     * @param <T>
     * @param name
     *            the property name
     * @param type
     *            the property type
     * @param bean
     *            the bean object
     * @return the property
     * @exception IllegalArgumentException
     *                If the specified type doesn't match the Property's type.
     * @exception NullPointerException
     *                if any of the parameters is null
     */
    public static <T> BeanProperty<T> getBeanPropertyByNameAndType(final String name,
        final Class<T> type, final Object bean){
        final Property<T> property =
            getPropertyByName(Introspection.getBeanInfo(checkNotNull(bean)),
                checkNotNull(name));
        checkArgument(checkNotNull(type).isAssignableFrom(property.getType()),
            "Bad type: %s for property: %s, required type: %s",
            type,
            name,
            property.getType());
        return new BeanProperty<T>(bean, property);
    }

    /**
     * Factory method: get a named Property from a Bean object. With this
     * method, there is no way to verify the property type. Use
     * {@link #getBeanPropertyByNameAndType(String, Class, Object)} if you need
     * type safety.
     * 
     * @param <T>
     *            the generic type
     * @param name
     *            the property name
     * @param bean
     *            the bean object
     * @return the property
     * @exception NullPointerException
     *                if any of the parameters is null
     */
    public static <T> BeanProperty<T> getBeanPropertyByName(final String name,
        final Object bean){
        final Property<T> property = getPropertyByName(bean, name);
        return new BeanProperty<T>(bean, property);
    }

}
